/*
 * 
 *  This file is part of Sourceprojects.org Lycia Copyright 2010 noctarius
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 * 
 */
package org.sourceprojects.lycia.fluent;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import org.sourceprojects.lycia.LyciaConfigurator;
import org.sourceprojects.lycia.LyciaParser;
import org.sourceprojects.lycia.LyciaParserFactory;
import org.sourceprojects.lycia.exceptions.LyciaConfigurationException;
import org.sourceprojects.lycia.fluent.XPathEventBuilder.BuildReturn;
import org.sourceprojects.lycia.internal.XPathAwareXmlParserConfigurator;

@SuppressWarnings("unchecked")
class FluentBuilderImpl<B> extends FluentBuilder<B> {
	// Parser lists
	private final List<TagDefinitionParserEventBuilder<B>> tagParsers = new ArrayList<TagDefinitionParserEventBuilder<B>>();
	private final List<RegexEventBuilder<B>> regexEventParsers = new ArrayList<RegexEventBuilder<B>>();
	private final List<XPathEventBuilder<B>> xpathEventParsers = new ArrayList<XPathEventBuilder<B>>();
	private final List<TagEventBuilder<B>> tagEventParsers = new ArrayList<TagEventBuilder<B>>();
	private final List<AnnotatedPojoEventBuilder> annotatedTagParserBuilders = new ArrayList<AnnotatedPojoEventBuilder>();
	private final List<ConfigurationBuilder<B>> configurationBuilders = new ArrayList<ConfigurationBuilder<B>>();

	private boolean androidCompatible = false;
	private boolean threadsafe = false;

	private ClassLoader classLoader = getClass().getClassLoader();

	@Override
	public LyciaParser<B> build() {
		String lyciaParserClass = LyciaParserFactory.STANDARD_LYCIA_PARSER_CLASS;
		String lyciaParserContextClass = LyciaParserFactory.STANDARD_PARSERCONTEXT_CLASS;

		if (androidCompatible) {
			lyciaParserClass = LyciaParserFactory.ANDROID_COMPATIBLE_LYCIA_PARSER_CLASS;
		}

		if (threadsafe) {
			lyciaParserContextClass = LyciaParserFactory.STANDARD_PARSERCONTEXT_THREADSAFE_CLASS;
		}

		final LyciaParser<B> xmlParser = LyciaParserFactory.newInstance(
				lyciaParserClass, lyciaParserContextClass, classLoader);

		Collections.sort(configurationBuilders,
				new ConfigurationBuilderPriorityComparator());

		for (final ConfigurationBuilder<B> configurationBuilder : configurationBuilders) {
			configurationBuilder.configure(xmlParser);
		}

		final LyciaConfigurator<B> configurator = xmlParser.getConfigurator();

		for (final TagDefinitionParserEventBuilder<B> tagParser : tagParsers) {
			configurator.addTagDefinitionParser(tagParser.build(configurator,
					xmlParser));
		}

		for (final AnnotatedPojoEventBuilder annotatedTagParserBuilder : annotatedTagParserBuilders) {
			configurator.addTagDefinitionParser(annotatedTagParserBuilder
					.build(configurator, xmlParser));
		}

		for (final TagEventBuilder<B> tagEventParser : tagEventParsers) {
			configurator.addTagDefinitionParser(tagEventParser.build(
					configurator, xmlParser));
		}

		for (final RegexEventBuilder<B> regexEventParser : regexEventParsers) {
			configurator.addTagDefinitionParser(regexEventParser.build(
					configurator, xmlParser));
		}

		final XPathAwareXmlParserConfigurator<B> xpathConfigurator = findXPathAwareConfigurator(configurator);
		for (final XPathEventBuilder<B> xpathEventParser : xpathEventParsers) {
			if (xpathConfigurator == null) {
				throw new LyciaConfigurationException(
						"No XPath aware XmlParserConfigurator was found but is needed for XPath parsing");
			}

			final BuildReturn<B> build = xpathEventParser.build(configurator,
					xmlParser);

			xpathConfigurator.registerXPathTagDefinitionParser(build.delegate,
					build.tag, build.namespaceURI);
		}

		return xmlParser;
	}

	@Override
	public FluentBuilder<B> configure(
			final ConfigurationBuilder... configurationBuilders) {

		for (ConfigurationBuilder<?> builder : configurationBuilders) {
			this.configurationBuilders.add((ConfigurationBuilder<B>) builder);
		}

		return this;
	}

	private class ConfigurationBuilderPriorityComparator implements
			Comparator<ConfigurationBuilder<B>> {

		@Override
		public int compare(final ConfigurationBuilder<B> o1,
				final ConfigurationBuilder<B> o2) {

			if (o1.getPriority() == o2.getPriority()) {
				return 0;
			}

			return o1.getPriority() < o2.getPriority() ? -1 : 1;
		}

	}

	private XPathAwareXmlParserConfigurator<B> findXPathAwareConfigurator(
			final LyciaConfigurator<B> configurator) {

		if (configurator instanceof XPathAwareXmlParserConfigurator) {
			return (XPathAwareXmlParserConfigurator<B>) configurator;
		}

		if (configurator.getParentXmlParserConfigurator() == null) {
			return null;
		}

		return findXPathAwareConfigurator(configurator
				.getParentXmlParserConfigurator());
	}

	@Override
	public FluentBuilder<B> makeAndroidCompatible() {
		androidCompatible = true;
		return this;
	}

	@Override
	public FluentBuilder<B> makeThreadsafe() {
		threadsafe = true;
		return this;
	}

	@Override
	public FluentBuilder<B> useClassLoader(final ClassLoader classLoader) {
		if (classLoader != null) {
			this.classLoader = classLoader;
		} else {
			this.classLoader = getClass().getClassLoader();
		}

		return this;
	}

	@Override
	public FluentBuilder<B> parser(final EventBuilder... eventBuilders) {
		for (final EventBuilder<?> eventBuilder : eventBuilders) {
			if (eventBuilder instanceof TagDefinitionParserEventBuilder<?>) {
				TagDefinitionParserEventBuilder<B> p;
				p = (TagDefinitionParserEventBuilder<B>) eventBuilder;
				tagParsers.add(p);
			} else if (eventBuilder instanceof XPathEventBuilder<?>) {
				xpathEventParsers.add((XPathEventBuilder<B>) eventBuilder);
			} else if (eventBuilder instanceof RegexEventBuilder<?>) {
				regexEventParsers.add((RegexEventBuilder<B>) eventBuilder);
			} else if (eventBuilder instanceof TagEventBuilder<?>) {
				tagEventParsers.add((TagEventBuilder<B>) eventBuilder);
			} else if (eventBuilder instanceof AnnotatedPojoEventBuilder<?>) {
				annotatedTagParserBuilders
						.add((AnnotatedPojoEventBuilder<B>) eventBuilder);
			}
		}

		return this;
	}

	@Override
	public FluentBuilder<B> transformer(final EventBuilder... eventBuilders) {
		return parser(eventBuilders);
	}
}
